`timescale 1ns / 1ps

// Vaild MEM_LATENCY = 1 or 2
module ROM_Loader
#(
    parameter WIDTH = 8,
    parameter DEPTH = 4,
    parameter MEM_FILE = "file.mem",
    parameter MEM_TYPE = "auto",
    parameter MEM_LATENCY = 1
)
(
    input   clk,
    input   rst,
    
    input   o_rdy,
    output  o_vld,
    output  [WIDTH-1: 0]    o_data
);

localparam S_MSB   = MEM_LATENCY;
localparam A_WIDTH = $clog2(DEPTH);
localparam S_INIT  = {{S_MSB{1'b0}}, 1'b1};
localparam S_WORK  = {1'b1, {S_MSB{1'b0}}};

// real reg
reg [S_MSB: 0]      cstate;

// combinatorial logic
reg [S_MSB: 0]      nstate;
reg all_en;
reg oo_vld;
wire [A_WIDTH-1: 0] addr;
wire [  WIDTH-1: 0] mem_out;

assign o_vld = oo_vld;

always @ (posedge clk)
begin
    if (rst)
        cstate <= S_INIT;
    else
        cstate <= nstate;
end

always @ (*)
begin
    if (cstate[S_MSB]) begin
        nstate = cstate;
    end
    else begin
        nstate = {cstate[S_MSB-1 : 0], 1'b0};
    end
end

always @ (*)
begin
    if (cstate[S_MSB]) begin
        all_en = o_rdy;
        oo_vld = 1'b1;
    end
    else begin
        all_en = 1'b1;
        oo_vld = 1'b0;
    end
end

zq_counter #(
    .N  (DEPTH)
) inst_cnt
(
    .clk    (clk),
    .rst    (rst),
    .clken  (all_en),
    .last   (),
    .out    (addr)
);

generate
    if (MEM_LATENCY == 1)
    begin
        assign o_data = mem_out;
    end
    else
    if (MEM_LATENCY == 2)
    begin
        reg [WIDTH-1: 0]    skid_data;
        reg                 skid_rdy;
        assign o_data = skid_rdy ? mem_out : skid_data;
        always @(posedge clk )
        begin
            if (rst)
            begin
                skid_rdy <= 1'b0;
            end
            else
            begin
                skid_rdy <= o_rdy;
                if (~cstate[S_MSB] | skid_rdy & ~o_rdy)
                    skid_data <= mem_out;
            end
        end
    end
endgenerate

xpm_memory_sprom #(
   .ADDR_WIDTH_A(A_WIDTH),          // DECIMAL
   .AUTO_SLEEP_TIME(0),             // DECIMAL
   .ECC_MODE("no_ecc"),             // String
   .MEMORY_INIT_FILE(MEM_FILE),     // String
   .MEMORY_INIT_PARAM(""),          // String
   .MEMORY_OPTIMIZATION("true"),    // String
   .MEMORY_PRIMITIVE(MEM_TYPE),     // String: auto, block, distributed, ultra 
   .MEMORY_SIZE(WIDTH*DEPTH),       // DECIMAL
   .MESSAGE_CONTROL(0),             // DECIMAL
   .READ_DATA_WIDTH_A(WIDTH),       // DECIMAL
   .READ_LATENCY_A(MEM_LATENCY),    // DECIMAL
   .READ_RESET_VALUE_A("0"),        // String
   .USE_MEM_INIT(1),                // DECIMAL
   .WAKEUP_TIME("disable_sleep")    // String
)
sprom_inst (
   .dbiterra(),                     // 1-bit output: Leave open.
   .douta(mem_out),                 // READ_DATA_WIDTH_A-bit output: Data output for port A read operations.
   .sbiterra(),                     // 1-bit output: Leave open.
   .addra(addr),                    // ADDR_WIDTH_A-bit input: Address for port A read operations.
   .clka(clk),                      // 1-bit input: Clock signal for port A.
   .ena(all_en),                    // 1-bit input: Memory enable signal for port A. Must be high on clock
                                    // cycles when read operations are initiated. Pipelined internally.

   .injectdbiterra(1'b0),           // 1-bit input: Do not change from the provided value.
   .injectsbiterra(1'b0),           // 1-bit input: Do not change from the provided value.
   .regcea(1'b1),                   // 1-bit input: Do not change from the provided value.
   .rsta(rst),                      // 1-bit input: Reset signal for the final port A output register stage.
                                    // Synchronously resets output port douta to the value specified by
                                    // parameter READ_RESET_VALUE_A.

   .sleep(1'b0)                    // 1-bit input: sleep signal to enable the dynamic power saving feature.
);

endmodule
